﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.IO;
using System.Threading;
using System.Diagnostics;
using System.Windows.Threading;
using System.Windows.Media.Imaging;
using System.Collections.Generic;

namespace System.Windows.Media.DirectShow.Filters
{
    internal class ElementCaptureSourceAsyncPin : BaseOutputPin
    {

        // Preferrered type
        private AMMediaType[] _mediaTypes = new AMMediaType[] { new AMMediaType {
            MajorType = MediaMajorTypes.Video,
            SubType = MediaSubTypes.ARGB,
            FixedSizeSamples = true,
            FourCC = "RGBA",
            Format = new BitmapInfoHeader()
        }};

        private ElementCaptureSource filter;
        private bool init;
        private WriteableBitmap bitmap;
        private UIElement element;

        private int pixelWidth;
        private int pixelHeight;

        private TimeSpan? startTime;
        private DispatcherTimer renderTimer = new DispatcherTimer();

        // Delay before render
        private TimeSpan bufferTime = TimeSpan.FromSeconds(0.2d);

        private Queue<IMediaSample> buffer = new Queue<IMediaSample>();

        public ElementCaptureSourceAsyncPin(ElementCaptureSource filter)
            : base(filter)
        {
            this.filter = filter;

            this.renderTimer.Tick += new EventHandler(renderTimer_Tick);
        }

        internal void SetCapture(UIElement element, int width, int height, TimeSpan renderInterval)
        {
            this.bitmap = new WriteableBitmap(width, height);
            this.element = element;
            this.pixelWidth = width;
            this.pixelHeight = height;
            this.renderTimer.Interval = renderInterval;
        }

        protected override void DecideBufferSize(IMemAllocator allocator, AllocatorProperties ap)
        {
            if (bitmap != null && (pixelWidth == 0 || pixelHeight == 0))
                throw new InvalidOperationException("Invalid element size. Ensure to call UpdateLayout");
            if (bitmap != null)
                ap.BufferSize = (pixelWidth * pixelHeight * 4); // 32 BIT

            base.DecideBufferSize(allocator, ap);
        }

        public override void Inactive()
        {
            base.Inactive();

            init = false;

            // Stop the timer
            this.Filter.Graph.Dispatcher.BeginInvoke(() =>
            {
                renderTimer.Stop();
            });
        }

        public override void Active()
        {
            base.Active();

            startTime = null;

            // Restart timer
            this.Filter.Graph.Dispatcher.BeginInvoke(() =>
            {
                this.renderTimer.Stop();
                this.renderTimer.Start();
            });
        }

        public override void Run(TimeSpan start)
        {
            base.Run(start);

            if (this.ConnectedPin == null) return;

            startTime = null;

            if (!init)
            {
                // Create allocator for the new size frame
                this.Allocator.Decommit();
                this.Allocator = this.DecideAllocator(this.ConnectedPin as IMemInputPin);
                this.Allocator.Commit();

                _mediaTypes[0].SampleSize = (int)this.Allocator.Properties.BufferSize;

                // Set format information
                BitmapInfoHeader bf = (BitmapInfoHeader)_mediaTypes[0].Format;
                bf.Width = pixelWidth;
                bf.Height = pixelHeight;
                bf.BitCount = 32;
                init = true;
            }

            this.Filter.Graph.Dispatcher.BeginInvoke(() =>
            {
                this.renderTimer.Start();
            });
        }

        private void renderTimer_Tick(object sender, EventArgs e)
        {
            try
            {
                // Render the element
                bitmap.Render(this.element, null);
                bitmap.Invalidate();

                DeliverFrame();
            }
            catch (Exception ex)
            {
                HandleException(ex);
            }
        }

        private bool revert;

        private void DeliverFrame()
        {
            revert = !revert;

            // Set when capturing is started
            if (startTime == null)
                startTime = this.Filter.SyncSource.GetTime();

            IMediaSample sample = this.GetDeliveryBuffer(TimeSpan.Zero, TimeSpan.Zero);

            // If null, allocator is not initialized
            if (sample != null)
            {
                sample.AddRef();
                try
                {
                    sample.StartMediaTime = 0;
                    sample.EndMediaTime = 0;
                    sample.StartTime = this.Filter.SyncSource.GetTime() - startTime.Value + bufferTime;
                    sample.EndTime = sample.StartTime.Add(renderTimer.Interval);
                    sample.MediaType = _mediaTypes[0];

                    int x = 0, y = 0;
                    int c = 0;
                    // Copy bitmap pixel to sample data
                    for (y = 0; y < pixelHeight; y++)
                    {
                        int r = y * pixelWidth;
                        for (x = 0; x < pixelWidth; x++)
                        {
                            byte[] rgb = BitConverter.GetBytes(bitmap.Pixels[x + r]);

                            // TEST
                            //if (revert)
                            //{
                            //    rgb[0] = 0;
                            //    rgb[1] = 0;
                            //    rgb[2] = 0;
                            //    rgb[3] = 255;
                            //}
                            //else
                            //{
                            //    rgb[0] = 255;
                            //    rgb[1] = 0;
                            //    rgb[2] = 0;
                            //    rgb[3] = 255;
                            //}

                            sample.Data[c++] = rgb[0];
                            sample.Data[c++] = rgb[1];
                            sample.Data[c++] = rgb[2];
                            sample.Data[c++] = rgb[3];
                        }
                    }

                    // Deliver the sample to the output pin
                    this.DeliverAsync(sample);
                }
                finally
                {
                    sample.Release();
                }
            }

        }

        private void HandleException(Exception e)
        {
            var d = new DirectShowFilterException("Error capturing element", this.Filter, e);
            if (this.Filter.Graph is IMediaEvent)
                ((IMediaEvent)this.Filter.Graph).NotifyEvent(new ErrorEventArgs(this.Filter, e));
            else
                throw d;
        }

        public override System.Collections.Generic.IEnumerable<AMMediaType> MediaTypes
        {
            get { return _mediaTypes; }
        }

        public override string Name
        {
            get { return "Stream Source Async Output Pin"; }
        }

    }
}
